package com.dlc.shop.config;

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.dlc.shop.bean.enums.ContentTypeEnum;
import com.dlc.shop.common.bean.UploadFile;
import com.dlc.shop.common.config.Constant;
import com.dlc.shop.common.enums.StorageTypeEnum;
import com.dlc.shop.common.exception.YamiShopBindException;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.PresignedPutObjectRequest;
import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest;

import java.net.URI;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 亚马逊文件上传s3协议配置类
 * @author gaozijie
 * @date 2023-10-25
 */
@Configuration
@RequiredArgsConstructor
public class AwsS3Config {

    private final ShopConfig shopConfig;

    private S3Client getS3Client(UploadFile config) {
        // aws-s3会自动转成桶域名进行访问(https://bucketName.endpoint)，minio不存在桶域名概念，需固定访问路径样式
        boolean isForcePathStyle = Objects.equals(StorageTypeEnum.MINIO.getValue(), config.getStorageType());
        return S3Client.builder()
                .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(config.getAccessKey(), config.getSecretKey())))
                .region(Region.AWS_GLOBAL)
                .endpointOverride(URI.create(config.getEndPoint()))
                .serviceConfiguration(S3Configuration.builder()
                        .pathStyleAccessEnabled(isForcePathStyle)
                        .chunkedEncodingEnabled(false)
                        .build())
                .build();
    }

    private S3Presigner getS3Presigner(UploadFile config) {
        // aws-s3会自动转成桶域名进行访问(https://bucketName.endpoint)，minio不存在桶域名概念，需固定访问路径样式
        boolean isForcePathStyle = Objects.equals(StorageTypeEnum.MINIO.getValue(), config.getStorageType());
        return S3Presigner.builder()
                .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(config.getAccessKey(), config.getSecretKey())))
                .region(Region.AWS_GLOBAL)
                .endpointOverride(URI.create(config.getEndPoint()))
                .serviceConfiguration(S3Configuration.builder()
                        .pathStyleAccessEnabled(isForcePathStyle)
                        .chunkedEncodingEnabled(false)
                        .build())
                .build();
    }

    /**
     * 获取预签名url
     * @param fileName 文件名（支持路径形式'xx/xx/xxx.png'）
     * @return 预签名url
     */
    public String getPreSignUrl(String fileName) {
        UploadFile config = this.getConfig();
        S3Presigner s3Presigner = this.getS3Presigner(config);
        PutObjectPresignRequest presignRequest = this.buildPreSignReq(fileName, config.getBucketName());
        PresignedPutObjectRequest presignedRequest = s3Presigner.presignPutObject(presignRequest);
        return presignedRequest.url().toString();
    }

    /**
     * 批量获取预签名url
     * @param fileNames 文件名集合（支持路径形式'xx/xx/xxx.png'）
     * @return 预签名url集合
     */
    public List<String> getBatchPreSignUrl(List<String> fileNames) {
        if (CollectionUtils.isEmpty(fileNames)) {
            return new ArrayList<>();
        }
        UploadFile config = this.getConfig();
        S3Presigner s3Presigner = this.getS3Presigner(config);
        List<String> preSignUrls = new ArrayList<>(fileNames.size());
        PresignedPutObjectRequest presignedRequest;
        for (String fileName : fileNames) {
            presignedRequest = s3Presigner.presignPutObject(this.buildPreSignReq(fileName, config.getBucketName()));
            preSignUrls.add(presignedRequest.url().toString());
        }
        return preSignUrls;
    }

    /**
     * 上传文件（通过预签名url）
     * @param preSignUrl 预签名url
     * @param fileData 文件二进制数据
     */
    public void uploadFileByPreSignUrl(String preSignUrl, byte[] fileData) {
        HttpRequest httpRequest = HttpRequest.put(preSignUrl)
                .body(fileData);
        try (HttpResponse resp = httpRequest.execute()) {
            if (!resp.isOk()) {
                throw new RuntimeException("presignUrl文件上传失败！原因为：" + resp.body());
            }
        }
    }

    /**
     * 上传文件
     * @param fileName 文件名（支持路径形式'xx/xx/xxx.png'）
     * @param fileData 文件二进制数据
     */
    public void uploadFile(String fileName, byte[] fileData) {
        UploadFile config = this.getConfig();
        S3Client s3Client = this.getS3Client(config);
        PutObjectRequest request = this.buildPutReq(fileName, config.getBucketName());
        s3Client.putObject(request, RequestBody.fromBytes(fileData));
    }

    /**
     * 删除文件
     * @param fileName 文件名（支持路径形式'xx/xx/xxx.png'）
     */
    public void deleteFile(String fileName) {
        UploadFile config = this.getConfig();
        S3Client s3Client = this.getS3Client(config);
        DeleteObjectRequest deleteObjectRequest = this.buildDeleteReq(fileName, config.getBucketName());
        s3Client.deleteObject(deleteObjectRequest);
    }

    /**
     * 批量删除文件
     * @param fileNames 文件名集合（支持路径形式'xx/xx/xxx.png'）
     */
    public void deleteBatchFile(List<String> fileNames) {
        if (CollectionUtils.isEmpty(fileNames)) {
            return;
        }
        UploadFile config = this.getConfig();
        S3Client s3Client = this.getS3Client(config);
        for (String fileName : fileNames) {
            s3Client.deleteObject(this.buildDeleteReq(fileName, config.getBucketName()));
        }
    }

    /**
     * 构建上传对象预签名请求
     * @param fileName 文件名
     * @param bucketName 桶名
     * @return 上传对象预签名请求
     */
    private PutObjectPresignRequest buildPreSignReq(String fileName, String bucketName) {
        return PutObjectPresignRequest.builder()
                .signatureDuration(Duration.ofSeconds(Constant.S3_URL_DURATION_SECONDS))
                .putObjectRequest(PutObjectRequest.builder()
                        .bucket(bucketName)
                        .key(fileName)
                        .build())
                .build();
    }

    /**
     * 构建上传对象请求
     * @param fileName 文件名
     * @param bucketName 桶名
     * @return 上传对象请求
     */
    private PutObjectRequest buildPutReq(String fileName, String bucketName) {
        return PutObjectRequest.builder()
                .bucket(bucketName)
                .key(fileName)
                .contentType(ContentTypeEnum.getContentType(fileName))
                .build();
    }

    /**
     * 构建删除对象请求
     * @param fileName 文件名
     * @param bucketName 桶名
     * @return 删除对象请求
     */
    private DeleteObjectRequest buildDeleteReq(String fileName, String bucketName) {
        return DeleteObjectRequest.builder()
                .bucket(bucketName)
                .key(fileName)
                .build();
    }

    /**
     * 获取文件上传配置，并检查参数
     * @return 文件上传配置
     */
    private UploadFile getConfig() {
        UploadFile config = shopConfig.getUploadFile();
        // 校验配置参数
        boolean isEmpty = ObjectUtils.isEmpty(config.getEndPoint())
                || ObjectUtils.isEmpty(config.getBucketName())
                || ObjectUtils.isEmpty(config.getAccessKey())
                || ObjectUtils.isEmpty(config.getSecretKey());
        if (isEmpty) {
            throw new YamiShopBindException("yami.upload.config.empty");
        }
        return config;
    }
}
